Add CST parity functions and optimize FRD workflows#22
Merged
Conversation
Phase 1 — Quick wins: Norm, Covar, Lsim, Augstate, SS2SS, Xperm, Inv, Pzmap, Isproper Phase 2 — PID controllers: NewPID (parallel form), NewPIDStd (standard form), PID2 (2-DOF), Pidtune (auto-tuning: P/I/PI/PD/PID/PIDF via open-loop shaping) Phase 3 — Analysis & canonical forms: Canon (modal + companion), Loopsens, Rss/Drss, Ssbal, Prescale Phase 4 — Frequency response data model: FRD type, FRDSeries/FRDParallel/FRDFeedback, FRD Nyquist/Sigma/Margin Phase 5 — Structural decomposition: Sminreal, Stabsep, Modsep (shared eigdecomp core with Schur fallback) Also fixes README inaccuracies (Norm, Lsim, InitialCondition). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. FRD.Sigma() MIMO: real block form doubles each SV; take every-other
value (vals[2*s]) instead of the first min(p,m). diag(2,1) now
correctly returns [2,1] not [2,2].
2. Delay metadata: Inv, Canon, Ssbal, Prescale, Sminreal, Stabsep, and
Modsep now reject systems with delays (HasDelay) instead of silently
dropping Delay/InputDelay/OutputDelay/LFT metadata.
3. Loopsens(P, C): changed from Loopsens(L) to accept plant and
controller separately. Computes true input-side sensitivities
Si=(I+CP)^{-1}, Ti=CP(I+CP)^{-1} via C*P loop, which differ from
output-side So/To for non-commutative MIMO. Added regression test
verifying Si != So.
4. FRDMargin: rewrote to use Bode()'s unwrapped phase instead of raw
cmplx.Phase, fixing missed crossings at ±180° wrap. Fixed WgFreq/
WpFreq field assignment to match existing Margin() convention.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FRDMargin: collect all crossings then apply same two-pass selection as Margin() — prefer smallest positive margin, fall back to any finite value. K=10/(s+1)^3 now returns GM≈-1.94dB matching Margin(). Lsim: validate that t vector is uniformly spaced (tol 1e-6 relative) before discretizing with dt=t[1]-t[0]; reject non-uniform grids with a clear error instead of silently producing wrong output. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Adds check in the discrete branch: abs(sys.Dt - dt)/sys.Dt <= 1e-6, so a discrete sys with Dt=0.1 fed a t grid at 0.05 spacing now errors instead of silently simulating with the wrong sample period. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
FRD stack (System.FRD, Sigma, Margin, Series, Parallel, Feedback): SISO/MIMO nw=200/2000/10000, large n=50 MIMO, 5x5 MIMO, negative-margin FRDMargin case Time response (Step, Impulse, Lsim): SISO n=2/10/50, MIMO n=10, Lsim 1e4 steps, discrete Lsim Loopsens: SISO n=2/10, MIMO n=10 Transform/decomposition (Canon, Stabsep, Modsep, Ssbal, Prescale, Sminreal, Inv, Covar): n=2/10/50/100 sweeps, SISO + MIMO Pidtune: PI, PID, PIDF on 4-state plant All fixtures use non-symmetric A (sub+superdiagonal coupling). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
System.FRD: single-alloc contiguous response storage via newFRDResponseStorage; bulk copy from FreqResponse.Data. FRD.Sigma: pre-allocate SVD workspace (block matrix, work array, singular values) once via newFRDSVDWorkspace, reuse across all frequency points; call impl.Dgesvd directly instead of mat.SVD. FRDSeries/Parallel: write into pre-allocated flat data slice via cMulNestedInto/cAddNestedInto instead of per-frequency [][]complex128. FRDFeedback: pre-allocate all scratch buffers (G, K, KG, I+KG, inv, aug) in newFRDFeedbackWorkspace; flat complex128 multiply/invert via cMulInto/cInvertInto; zero per-iteration allocations. benchstat (geomean): -14% time, -22% memory SystemFRD SISO nw=10k: -50% time, -99.7% allocs FRDSigma MIMO nw=10k: -14% time, -93% memory FRDFeedback SISO nw=2k: -74% time, -58% memory FRDFeedback MIMO nw=10k: -54% time, -81% memory FRDSeries MIMO nw=10k: -39% time, -99.9% allocs Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Absolute 1e-10 tolerance breaks for eigenvalues with magnitude >>1 where machine eps on the imaginary part exceeds the threshold. Use 1e-10*max(1, |lambda|) consistent with eigdecomp.go's isConjugate. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Search for a conjugate partner before collapsing a small-imaginary eigenvalue to a real modal block, add a regression test for the large-real/small-imag pair case, and rename bench_new_test.go to benchmark_hotspots_test.go. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Lsim,FRDMargin, and modal eigenvalue classificationSystem.FRD,FRD.Sigma,FRDFeedback, and related FRD benchmark paths, and renamebench_new_test.gotobenchmark_hotspots_test.goTesting